package brainfsck

import chisel3._
import chisel3.util._

class LsfrRng(initState: UInt, mask: UInt) extends Module {
    require(initState.getWidth==mask.getWidth, "The state and mask must have the same width.")
    val randomNumber = IO(Output(UInt()))

    private val state = RegInit(initState)
    state := (state & mask).xorR ## state(state.getWidth-1, 1)
    randomNumber := state
}

class RandomDelay(xlen: Int, readDealy: Boolean, writeDelay: Boolean) extends Module {
    val io = IO(new Bundle{
        val writePort = Flipped(Decoupled(UInt(xlen.W)))
        val readPort  = Decoupled(UInt(xlen.W))
    })

    private val buffer = Reg(UInt(xlen.W))
    private val bufferValid = RegInit(false.B)
    io.readPort.bits := buffer

    private val writeHappens = io.writePort.ready && io.writePort.valid
    private val readHappens  = io.readPort.ready && io.readPort.valid

    buffer := Mux(writeHappens, io.writePort.bits, buffer)
    bufferValid := MuxCase(bufferValid, Seq(
        writeHappens -> true.B,
        readHappens  -> false.B,
    ))

    private val rng = Module(new LsfrRng(0x5eed.U(16.W), 0xb400.U(16.W)))

    // This assumes that ready can be deasserted.
    io.writePort.ready := !bufferValid && (!writeDelay.B || rng.randomNumber(15))
    // This ensures that valid cannot be deasserted.
    private val readValidHold = RegInit(false.B)
    readValidHold := Mux(writeHappens, rng.randomNumber(0), readValidHold||rng.randomNumber(0))
    io.readPort.valid := bufferValid && (!readDealy.B || readValidHold)
}
